#include <hd.h>
#include <port.h>
#include <memory.h>

#define ATA_INFO        0xEC            // 获取硬盘信息
#define ATA_READ        0x20            // 读取扇区数据
#define ATA_WRITE       0x30            // 数据写入扇区

// 硬盘 I/O 端口及寄存器定义
#define REG_DEV_CTRL    0x3F6
#define REG_DATA        0x1F0
#define REG_FEATURES    0x1F1
#define REG_ERROR       0x1F1
#define REG_NSECTOR     0x1F2
#define REG_LBA_LOW     0x1F3
#define REG_LBA_MID     0x1F4
#define REG_LBA_HIGH    0x1F5
#define REG_DEVICE      0x1F6
#define REG_STATUS      0x1F7
#define REG_COMMAND     0x1F7

// 硬盘状态位定义
#define	STATUS_BUSY     0x80
#define	STATUS_DRDY     0x40
#define	STATUS_DFSE     0x20
#define	STATUS_DSC      0x10
#define	STATUS_DRQ      0x08
#define	STATUS_CORR     0x04
#define	STATUS_IDX      0x02
#define	STATUS_ERR      0x01

typedef enum E_STATUS
{
    E_NOT_READY,
    E_READY
} E_STATUS;

/******************************************************************************
* 函数名称: static E_STATUS WaitReady(void)
* 功能说明: 等待硬盘设备就绪
* 输入参数: 无
* 输出参数: 无
* 函数返回: E_NOT_READY:未就绪; E_READY:已就绪
* 其它说明: bi7:BUSY = 0 且 bit4:DRQ = 1 时表示硬盘设备已就绪，可以进行读写操作
******************************************************************************/
static E_STATUS WaitReady(void)
{
    U32 i = 0;
    U08 status = 0;

    for(i = 0; i < 500; i++)
    {
        status = ReadPort(REG_STATUS);
        // bi7:BUSY = 0 且 bit4:DRQ = 1 时表示硬盘设备已就绪，可以进行读写操作
        if((!(status & STATUS_BUSY)) && (status & STATUS_DRQ))
            return E_READY;
    }

    return E_NOT_READY;
}

/******************************************************************************
* 函数名称: void HardDiskInit(void)
* 功能说明: 硬盘初始化
* 输入参数: 无
* 输出参数: 无
* 函数返回: 无
* 其它说明: 无
******************************************************************************/
void HardDiskInit(void)
{
    GetHardDiskSectors();
}

/******************************************************************************
* 函数名称: E_RET HardDiskRead(U32 sn, U08* buf)
* 功能说明: 读硬盘 sn 号扇区中的数据到 buf 中
* 输入参数: U32 sn      --扇区号
    　　　　U08* buf    --数据区
* 输出参数: 无
* 函数返回: E_OK:成功; E_ERR:失败
* 其它说明: 一次性读取一整个扇区 SEC_SIZE 字节数据
******************************************************************************/
E_RET HardDiskRead(U32 sn, U08* buf)
{
    // 检查参数合法性
    if(sn >= GetHardDiskSectors() || NULL == buf)
        return E_ERR;

    WritePort(REG_NSECTOR, 1);                  // 设置要读取的扇区数，固定每次只读取一个扇区
    WritePort(REG_LBA_LOW, (sn&0xFF));          // 设置 LBA Low, 扇区号低 8 位
    WritePort(REG_LBA_MID, ((sn>>8)&0xFF));     // 设置 LBA Min, 扇区号中 8 位
    WritePort(REG_LBA_HIGH, ((sn>>16)&0xFF));   // 设置 LBA High, 扇区号高 8 位
    WritePort(REG_DEVICE, (((sn>>24)&0x0F) | 0xE0));   // 设置 Device,  bit4 ~ bit7 为 1110(0xE0)，表示 LBA 模式，低 4 位与LBA low Mid High 共拼成 28 位扇区地址寻址

    WritePort(REG_COMMAND, ATA_READ);           // 读扇区命令

    // 等待硬盘设备就绪
    if(E_NOT_READY == WaitReady())
        return E_ERR;
    
    ReadPortW(REG_DATA, (U16 *)buf, SEC_SIZE>>1);

    return E_OK;
}

/******************************************************************************
* 函数名称: E_RET HardDiskWrite(U32 sn, U08* buf)
* 功能说明: 将 buf 中的数据写入到硬盘 sn 号扇区中
* 输入参数: U32 sn      --扇区号
    　　　　U08* buf    --数据区
* 输出参数: 无
* 函数返回: E_OK:成功; E_ERR:失败
* 其它说明: 一次性写入一整个扇区 SEC_SIZE 字节数据
******************************************************************************/
E_RET HardDiskWrite(U32 sn, U08* buf)
{
    // 检查参数合法性
    if(sn >= GetHardDiskSectors() || NULL == buf)
        return E_ERR;

    WritePort(REG_NSECTOR, 1);                  // 设置要读取的扇区数，固定每次只读取一个扇区
    WritePort(REG_LBA_LOW, (sn&0xFF));          // 设置 LBA Low, 扇区号低 8 位
    WritePort(REG_LBA_MID, ((sn>>8)&0xFF));     // 设置 LBA Min, 扇区号中 8 位
    WritePort(REG_LBA_HIGH, ((sn>>16)&0xFF));   // 设置 LBA High, 扇区号高 8 位
    WritePort(REG_DEVICE, (((sn>>24)&0x0F) | 0xE0));   // 设置 Device,  bit4 ~ bit7 为 1110(0xE0)，表示 LBA 模式，低 4 位与LBA low Mid High 共拼成 28 位扇区地址寻址

    WritePort(REG_COMMAND, ATA_WRITE);          // 写扇区命令

    // 等待硬盘设备就绪
    if(E_NOT_READY == WaitReady())
        return E_ERR;
    
    WritePortW(REG_DATA, (U16 *)buf, SEC_SIZE>>1);

    return E_OK;
}

/******************************************************************************
* 函数名称: U32 GetHardDiskSectors(void)
* 功能说明: 获取硬盘总扇区数
* 输入参数: 无
* 输出参数: 无
* 函数返回: 返回硬盘总扇区数
* 其它说明: 无
******************************************************************************/
U32 GetHardDiskSectors(void)
{
    static U32 ret = 0;
    U16* buf = NULL;

    // 扇区数只读取一次就可以了
    if(ret)
        return ret;

    WritePort(REG_COMMAND, ATA_INFO);          // 读硬盘信息

    // 申请一个扇区大小的内存空间用于存放读取到的硬盘信息
    // 因为硬盘数据是以双字节为基本单位，申请到的内存就相当于数组 U16 buf[SEC_SIZE/2]
    // buf[61] 为硬盘扇区数的高 16 位，buf[60] 为硬盘扇区数的低 16 位
    buf = (U16 *)Malloc(SEC_SIZE);
    if(NULL == buf)
        return 0;

    // 等待硬盘设备就绪
    if(E_NOT_READY == WaitReady())
        return 0;

    ReadPortW(REG_DATA, (U16 *)buf, SEC_SIZE>>1);
    ret = (buf[61]<<16) | buf[60];
    Free(buf);

    return ret;
}

